#!/usr/bin/env wowlua
require("config") -- configuration
require("utils") -- provides dump()
require("profile")
require("log") -- provides log()
require("cache") -- provides cache
require("compat") -- provides compatibility (string.join, ...)
require("bindings") -- provides bindings related functions
require("cvar") -- provide cvar related functions
require("wtf") -- wtf file parsing
require("db") -- databases
require("units") -- units data

dofile("global.lua")
dofile("call.lua")

dofile("addons.lua")
dofile("widgets.lua")
dofile("security.lua")
dofile("protected.lua")

dofile("api.lua")

pcall(dofile, "events.lua")

if config.LUALINT then
	package.path = package.path .. ";../lualint/?.lua"
	require"lualint"
end

Simulator = {}
local sim = Simulator

function sim:Initialize()
	if config.PROFILE_GLOBAL then
		for name, value in pairs(api.global) do
			if type(value) == "function" and name ~= "type" then
				api.global[name] = profile.Wrap(name, value)
			end
		end
	end
	wtf.Parse("wtf/bindings-cache.wtf")

	cvar.Initialize()
	Security:initialize()

	Addons.ReadTocInfo("FrameXML", config.BASE_PATH, "SECURE")
	for _, addon in ipairs(config.BLIZZARD_ADDONS) do
		Addons.ReadTocInfo("Blizzard_"..addon, config.BASE_PATH.."/AddOns", "SECURE")
	end
	Addons.AfterBlizzardAddons()
	for name, path in pairs(config.ADDONS) do
		Addons.ReadTocInfo(name, path, "INSECURE")
	end
	Addons.ResolveDependencies()

	cvar.Set("realmName", config.REALM)
	cvar.Set("accountName", config.ACCOUNT)

	units.InitUnitsAuras()
end

function sim:LoadState()
	cvar.Load()
	bindings.Load(0)
	Addons.LoadDisabledList()
end

function sim:SaveState()
	Addons.SaveDisabledList()
	Addons.SaveRegisteredData()
	cvar.Save()
	bindings.Save(1)
end

function sim:LoadAddOns()
	Addons.LoadAllAddons()
end

sim.EventHandlers = {
	update = function (self, entry)
		UI.Update(entry.elapsed or 0.01)
	end,
	event = function (self, entry)
		if type(entry.params) == "table" then
			UI.BroadcastEvent(entry.event, unpack(entry.params))
		elseif entry.params ~= nil then
			UI.BroadcastEvent(entry.event, entry.params)
		else
			UI.BroadcastEvent(entry.event)
		end
	end,
	script = function (self, entry)
		api.call(entry.content)
	end,
	key = function (self, entry)
		if entry.key then
			bindings.Handle(entry.key, entry.state or "down")
		elseif entry.name then
			bindings.Run(entry.name, entry.state or "down")
		end
	end,
	text = function (self, entry)
		local edit = api.current.ChatFrameEditBox
		edit:SetText(entry.content)
		UI.CallScript(edit, "OnEnterPressed")
	end,
--	macro = function (self, entry)
--		macro.Run(entry.content)
--	end,
	func = function (self, entry)
		entry.func()
	end,
	breakpoint = function (self, entry)
		print("Breakpoint reached")
		debug.debug()
	end,
	update_unit = function (self, entry)
		local unit = entry.unit
		if unit == "all" then
			for unit in pairs(db.vunits) do
				self:UpdateUnit(unit)
			end
		else
			self:UpdateUnit(unit)
		end
	end,
	svg = function (self, entry)
			UI.DumpFrames(entry.file, require"svg")
	end,
	html_canvas = function (self, entry)
			UI.DumpFrames(entry.file, require"html_canvas")
	end,
}

local unitEvents = {
	"UNIT_NAME_UPDATE",
	"UNIT_HEALTH",
	"UNIT_MAXHEALTH",
	"UNIT_MANA",
	"UNIT_MAXMANA",
	"UNIT_AURA",
}

local playerEvents = {
	"PLAYER_AURAS_CHANGED",
}
function sim:UpdateUnit(unit)
	for _, event in ipairs(unitEvents) do
		UI.BroadcastEvent(event, unit)
	end
	if unit == "player" then
		for _, event in ipairs(playerEvents) do
			UI.BroadcastEvent(event, unit)
		end
	end
end

function sim:PlaySequence(sequence)
	for _, entry in ipairs(sequence) do
		local handler = self.EventHandlers[entry.type]
		if handler then
			handler(self, entry)
		end
	end
end

function sim:Stop()
	self:PlaySequence(config.LOGOUT_SEQUENCE)
	self:SaveState()

	profile.Dump()
end

function sim:Run()
	self:Initialize()
	self:LoadState()
	local loader
	if config.NO_RUN or NORUN then
		loader = function()
			self:LoadAddOns()
			self:PlaySequence(config.LOGIN_SEQUENCE)
		end
	else
		loader = function ()
			self:LoadAddOns()
			self:PlaySequence(config.LOGIN_SEQUENCE)
			self:PlaySequence(config.PLAY_SEQUENCE)
		end
	end
	if config.DEBUG_PROFILE then
		profile.Profile(loader)
	else
		loader()
	end

	if config.DUMP then
		UI.DumpEvents()
		UI.DumpFrameHierarchy(true)
		Security:DumpTaints()
		bindings.Dump()
		api.dumpfailedglobals()
	end

	self:Stop()
end

function main()
	if arg and arg[1] == "clear" then
		local status, lfs = pcall(require, "lfs")
		if not status then
			io.stderr:write(string.format("lfs is required to clear the cache : %s\n", lfs))
		else
			for filename in lfs.dir("cache") do
				if filename ~= "." and filename ~= ".." then
					pcall(os.remove, "cache/" .. filename)
				end
			end
			for filename in lfs.dir("tempconv") do
				if filename ~= "." and filename ~= ".." then
					pcall(os.remove, "tempconv/" .. filename)
				end
			end
		end
	end
	sim:Run()
end

function pwd(level)
	local i = debug.getinfo(level or 5)
	print(("%s:%d"):format(i.source, i.currentline))
end

main()
